5.2 方法

方法能给用户定义的类型添加新的行为。方法实际上也是函数,只是在声明时,在关键字 func 和方法名之间增加了一个参数,如代码清单5-11所示。

代码清单5-11 listing11.go

01 // 这个示例程序展示如何声明
02 // 并使用方法
03 package main
04
05 import (
06   "fmt"
07 )
08
09 // user在程序里定义一个用户类型
10 type user struct {
11   name string
12   email string
13 }
14
15 // notify使用值接收者实现了一个方法
16 func (u user) notify() {
17   fmt.Printf("Sending User Email To %s<%s>\n",
18     u.name,
19     u.email)
20 }
21
22 // changeEmail使用指针接收者实现了一个方法
23 func (u *user) changeEmail(email string) {
24   u.email = email
25 }
26
27 // main是应用程序的入口
28 func main() {
29   // user类型的值可以用来调用
30   // 使用值接收者声明的方法
31   bill := user{"Bill", "bill@email.com"}
32   bill.notify()
33
34   // 指向user类型值的指针也可以用来调用
35   // 使用值接收者声明的方法
36   lisa := &user{"Lisa", "lisa@email.com"}
37   lisa.notify()
38
39   // user类型的值可以用来调用
40   // 使用指针接收者声明的方法
41   bill.changeEmail("bill@newdomain.com")
42   bill.notify()
43
44   // 指向user类型值的指针可以用来调用
45   // 使用指针接收者声明的方法
46   lisa.changeEmail("lisa@newdomain.com")
47   lisa.notify()
48 }

代码清单5-11的第16行和第23行展示了两种类型的方法。关键字 func 和函数名之间的参数被称作 接收者 ,将函数与接收者的类型绑在一起。如果一个函数有接收者,这个函数就被称为 方法 。当运行这段程序时,会得到代码清单5-12所示的输出。

代码清单5-12 listing11.go的输出

Sending User Email To Bill<bill@email.com>
Sending User Email To Lisa<lisa@email.com>
Sending User Email To Bill<bill@newdomain.com>
Sending User Email To Lisa<lisa@comcast.com>

让我们来解释一下代码清单5-13所示的程序都做了什么。在第10行,该程序声明了名为 user 的结构类型,并声明了名为 notify 的方法。

代码清单5-13 listing11.go:第09行到第20行

09 // user在程序里定义一个用户类型
10 type user struct {
11   name string
12   email string
13 }
14
15 // notify使用值接收者实现了一个方法
16 func (u user) notify() {
17   fmt.Printf("Sending User Email To %s<%s>\n",
18     u.name,
19     u.email)
20 }

Go语言里有两种类型的接收者: 值接收者指针接收者 。在代码清单5-13的第16行,使用值接收者声明了 notify 方法,如代码清单5-14所示。

代码清单5-14 使用值接收者声明一个方法

func (u user) notify() {

notify 方法的接收者被声明为 user 类型的值。如果使用值接收者声明方法,调用时会使用这个值的一个副本来执行。让我们跳到代码清单5-11的第32行来看一下如何调用 notify 方法,如代码清单5-15所示。

代码清单5-15 listing11.go:第29行到第32行

29   // user类型的值可以用来调用
30   // 使用值接收者声明的方法
31   bill := user{"Bill", "bill@email.com"}
32   bill.notify()

代码清单5-15展示了如何使用 user 类型的值来调用方法。第31行声明了一个 user 类型的变量 bill ,并使用给定的名字和电子邮件地址做初始化。之后在第32行,使用变量 bill 来调用 notify 方法,如代码清单5-16所示。

代码清单5-16 使用变量来调用方法

bill.notify()

这个语法与调用一个包里的函数看起来很类似。但在这个例子里, bill 不是包名,而是变量名。这段程序在调用 notify 方法时,使用 bill 的值作为接收者进行调用,方法 notify 会接收到 bill 的值的一个副本。

也可以使用指针来调用使用值接收者声明的方法,如代码清单5-17所示。

代码清单5-17 listing11.go:第34行到第37行

34   // 指向user类型值的指针也可以用来调用
35   // 使用值接收者声明的方法
36   lisa := &user{"Lisa", "lisa@email.com"}
37   lisa.notify()

代码清单5-17展示了如何使用指向 user 类型值的指针来调用 notify 方法。在第36行,声明了一个名为 lisa 的指针变量,并使用给定的名字和电子邮件地址做初始化。之后在第37行,使用这个指针变量来调用 notify 方法。为了支持这种方法调用,Go语言调整了指针的值,来符合方法接收者的定义。可以认为Go语言执行了代码清单5-18所示的操作。

代码清单5-18 Go在代码背后的执行动作

(*lisa).notify()

代码清单5-18展示了Go编译器为了支持这种方法调用背后做的事情。指针被解引用为值,这样就符合了值接收者的要求。再强调一次, notify 操作的是一个副本,只不过这次操作的是从 lisa 指针指向的值的副本。

也可以使用指针接收者声明方法,如代码清单5-19所示。

代码清单5-19 listing11.go:第22行到第25行

22 // changeEmail使用指针接收者实现了一个方法
23 func (u *user) changeEmail(email string) {
24   u.email = email
25 }

代码清单5-19展示了 changeEmail 方法的声明。这个方法使用指针接收者声明。这个接收者的类型是指向 user 类型值的指针,而不是 user 类型的值。当调用使用指针接收者声明的方法时,这个方法会共享调用方法时接收者所指向的值,如代码清单5-20所示。

代码清单5-20 listing11.go:第36行和第44行到第46行

36   lisa := &user{"Lisa", "lisa@email.com"}
44   // 指向user类型值的指针可以用来调用
45   // 使用指针接收者声明的方法
46   lisa.changeEmail("lisa@newdomain.com")

在代码清单5-20中,可以看到声明了 lisa 指针变量,还有第46行使用这个变量调用了 changeEmail 方法。一旦 changeEmail 调用返回,这个调用对值做的修改也会反映在 lisa 指针所指向的值上。这是因为 changeEmail 方法使用了指针接收者。总结一下,值接收者使用值的副本来调用方法,而指针接收者使用实际值来调用方法。

也可以使用一个值来调用使用指针接收者声明的方法,如代码清单5-21所示。

代码清单5-21 listing11.go:第31行和第39行到第41行

31   bill := user{"Bill", "bill@email.com"}
39   // user类型的值可以用来调用
40   // 使用指针接收者声明的方法
41   bill.changeEmail("bill@newdomain.com")

在代码清单5-21中可以看到声明的变量 bill ,以及之后使用这个变量调用使用指针接收者声明的 changeEmail 方法。Go语言再一次对值做了调整,使之符合函数的接收者,进行调用,如代码清单5-22所示。

代码清单5-22 Go在代码背后的执行动作

(&bill).changeEmail("bill@newdomain.com")

代码清单5-22展示了Go编译器为了支持这种方法调用在背后做的事情。在这个例子里,首先引用 bill 值得到一个指针,这样这个指针就能够匹配方法的接收者类型,再进行调用。Go语言既允许使用值,也允许使用指针来调用方法,不必严格符合接收者的类型。这个支持非常方便开发者编写程序。

应该使用值接收者,还是应该使用指针接收者,这个问题有时会比较迷惑人。可以遵从标准库里一些基本的指导方针来做决定。后面会进一步介绍这些指导方针。

results matching ""

    No results matching ""